﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Security.Cryptography;
using System.Text.RegularExpressions;

namespace Xenta.Utils
{
    /// <summary>
    /// Represents the string helper.
    /// </summary>
    public static class StringHelper
    {
        #region Constants

        private static readonly Regex PATTERN_GUID = 
            new Regex(@"^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$", 
                RegexOptions.Compiled | 
                RegexOptions.Singleline);
        private static readonly Regex PATTERN_EMAIL = 
            new Regex(@"[\w\.-]+(\+[\w-]*)?@([\w-]+\.)+[\w-]+", 
                RegexOptions.Compiled | 
                RegexOptions.Singleline);

        #endregion

        #region Methods

        /// <summary>
        /// Determines if the input string represents global unique identifier.
        /// </summary>
        /// <param name="s">The input string.</param>
        /// <returns>True if the string represents global unique identifier; otherwise false.</returns>
        public static bool IsGuid(string s)
        {
            return IsMatch(s, PATTERN_GUID);
        }

        /// <summary>
        /// Determines if the input string represents email.
        /// </summary>
        /// <param name="s">The input string.</param>
        /// <returns>True if the string represents email; otherwise false.</returns>
        public static bool IsEmail(string s)
        {
            return IsMatch(s, PATTERN_EMAIL);
        }

        /// <summary>
        /// Generates the random string.
        /// </summary>
        /// <param name="length">The string length.</param>
        /// <param name="allowSpecChars">A value indicating whether non-alphanumeric characters are allowed.</param>
        /// <returns>The randomly generated string.</returns>
        public static string Random(int length, bool allowSpecChars = false)
        {
            if(length <= 0)
                return String.Empty;

            var buf = new byte[length];
            var strBuf = new char[length];
            var charBuf = new[] { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 
                'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 
                'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 
                'U', 'V', 'W', 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' };
            var specCharBuf = new[] { '!', '@', '@', '$', '%', '^', '^', '*', '(', ')', '_', 
                '-', '+', '=', '[', '{', ']', '}', ';', ':', '>', '|', '.', '/', '?' };

            using(var csp = new RNGCryptoServiceProvider())
            {
                csp.GetNonZeroBytes(buf);
                for(int i = 0; i <= length - 1; i++)
                {
                    int rndChr = (buf[i] % (allowSpecChars ? charBuf.Length + specCharBuf.Length - 1 : charBuf.Length - 1));
                    strBuf[i] = rndChr < charBuf.Length ? charBuf[rndChr] : specCharBuf[rndChr - charBuf.Length];
                }
            }
            return new String(strBuf);
        }

        /// <summary>
        /// Parses the input string into specified type.
        /// </summary>
        /// <typeparam name="T">The target type to convert.</typeparam>
        /// <param name="s">The input string.</param>
        /// <param name="defaultValue">The default value to return if convertation fails.</param>
        /// <returns>The parsed value on success; otherwise returns the default value.</returns>
        public static T ParseAs<T>(string s, T defaultValue = default(T))
        {
            return (T)ParseAs(s, typeof(T), defaultValue);
        }

        /// <summary>
        /// Parses the input string into specified type.
        /// </summary>
        /// <param name="s">The input string.</param>
        /// <param name="t">The target type to convert.</param>
        /// <param name="defaultValue">The default value to return if convertation fails.</param>
        /// <returns>The parsed value on success; otherwise returns the default value.</returns>
        public static object ParseAs(string s, Type t, object defaultValue)
        {
            var result = defaultValue;

            try
            {
                if(!String.IsNullOrEmpty(s))
                {
                    var nut = Nullable.GetUnderlyingType(t);
                    if(t.IsEnum || (nut != null && nut.IsEnum))
                    {
                        result = Enum.Parse(nut ?? t, s, true);
                    }
                    else
                    {
                        var tc = TypeDescriptor.GetConverter(t);
                        if(tc.IsValid(s))
                            result = tc.ConvertFromInvariantString(s);
                    }
                }
            }
            catch
            {
            }
            return result;
        }

        /// <summary>
        /// Trims the input string to required length.
        /// </summary>
        /// <param name="s">The input string.</param>
        /// <param name="length">The required lenght.</param>
        /// <param name="applyStringTrim">A value indicates whether standard String.Trim can be applied.</param>
        /// <returns>The trimmed string.</returns>
        public static string TrimToLength(string s, int length, bool applyStringTrim = true)
        {
            if(String.IsNullOrEmpty(s))
                return s;
            if(applyStringTrim)
                s = s.Trim();
            if(s.Length <= length)
                return s;
            return s.Substring(0, length);
        }

        /// <summary>
        /// Converts the null string to the empty string.
        /// </summary>
        /// <param name="s">The input string.</param>
        /// <returns>If the input string is null, returns the empty string; otherwise returns the input string.</returns>
        public static string EnsureNotNull(string s)
        {
            return String.IsNullOrEmpty(s) ? String.Empty : s;
        }

        /// <summary>
        /// Receives a string and returns the string with its letters reversed.
        /// </summary>
        /// <param name="s">The input string.</param>
        /// <returns>The reversed string.</returns>
        public static string Reverse(string s)
        {
            if(String.IsNullOrWhiteSpace(s))
                return s;
            var chars = s.ToCharArray();
            Array.Reverse(chars);
            return new String(chars);
        }

        /// <summary>
        /// Checks, if an input string matches the required pattern.
        /// </summary>
        /// <param name="s">The input string.</param>
        /// <param name="pattern">The pattern.</param>
        /// <returns>true it matches; otherwise false.</returns>
        public static bool IsMatch(string s, string pattern)
        {
            if(String.IsNullOrWhiteSpace(pattern))
                return true;
            return Regex.IsMatch(s, pattern);
        }

        /// <summary>
        /// Checks, if an input string matches the required pattern.
        /// </summary>
        /// <param name="s">The input string.</param>
        /// <param name="pattern">The pattern.</param>
        /// <returns>true it matches; otherwise false.</returns>
        public static bool IsMatch(string s, Regex pattern)
        {
            return pattern.IsMatch(s);
        }

        /// <summary>
        /// Concatenates all the elements of a string array, using the specified separator
        /// between each element.
        /// </summary>
        /// <param name="separator">The string to use as a separator.</param>
        /// <param name="stringArray">An array that contains the elements to concatenate.</param>
        /// <param name="trailingSeparator">A value indicates whether a trailing separator is required.</param>
        /// <returns>A string that consists of the elements in value delimited by the separator string.</returns>
        public static string Merge(IEnumerable<String> stringArray, 
            string separator, 
            bool trailingSeparator = false)
        {
            string value = String.Join(separator, stringArray);
            if(trailingSeparator)
                value = String.Concat(value, separator);
            return value;
        }

        /// <summary>
        /// Converts the specified string to titlecase.
        /// </summary>
        /// <param name="s">The input string.</param>
        /// <returns>The converted string.</returns>
        public static string Capitalize(string s)
        {
            if(String.IsNullOrWhiteSpace(s))
                return s;
            return CultureInfo.InvariantCulture.TextInfo.ToTitleCase(s);
        }

        #endregion
    }
}
